#include "Source/IO/File.h"
#include <string>
#include <cstdio>
#include <boost/shared_ptr.hpp>
#include "json.h"
#include <cassert>

CL_File::CL_File() :
m_sFilename(""),
m_bIsOpen(false),
m_bEof(false),
m_filemode(E_FileModes_None),
m_uLength(0)
{
	m_pFile = NULL;
}


CL_File::~CL_File()
{
	if(m_bIsOpen)
		Close();
}

CL_File* CL_File::Open(const std::string & filename, E_FileModes filemode)
{
	CL_File* file = new CL_File();

	file->m_sFilename = filename;
	file->m_filemode = filemode;

	file->m_pFile = fopen(filename.c_str(), CL_File::ObtainCFilemode(filemode, false, filename).c_str());
	file->m_bIsOpen = true;

	return file;
}

void CL_File::Close()
{
	assert( this != NULL && this->m_pFile != NULL && m_bIsOpen == true);

	m_bIsOpen = false;
	fclose(m_pFile);
	m_pFile = NULL;

}

std::string CL_File::GetFilename()
{
	return m_sFilename;
}

bool CL_File::IsOpen()
{
	return m_bIsOpen;
}

E_FileModes	CL_File::GetFilemode()
{
	return m_filemode;
}

void CL_File::SwitchFilemode(E_FileModes filemode)
{
	assert(m_bIsOpen);

	Close();
	m_pFile = fopen(m_sFilename.c_str(), CL_File::ObtainCFilemode(filemode, true, m_sFilename).c_str());
	m_filemode = filemode;
	m_bIsOpen = true;
}

bool CL_File::IsEOF()
{
	return m_bEof;
}
//----------------------READING-----------------------//


void CL_File::ReadInt(int & buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	fread(&buffer, sizeof(int), 1, m_pFile);
}

void CL_File::ReadFloat(float & buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	fread(&buffer, sizeof(float), 1, m_pFile);
}

void CL_File::ReadDouble(double & buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	fread(&buffer, sizeof(double), 1, m_pFile);
}

void CL_File::ReadChar(char & buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	fread(&buffer, sizeof(char), 1, m_pFile);
}

void CL_File::ReadString(std::string & buffer, int length)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	char* cBuffer = new char[length+1]; //allocate for the '\0' as well
	fread(cBuffer, sizeof(char), length, m_pFile);
	cBuffer[length] = '\0';

	buffer = cBuffer;

	delete [] cBuffer;
	cBuffer = NULL;
}

void CL_File::ReadStringLengthPrefix(std::string & result)
{
	//Reads in an integer first that represents the length
	//Then reads a string with the obtained length

	//This is to be used in our own formats for ease

	assert( m_bIsOpen && m_filemode==E_FileModes_ReadBinary);

	int length;
	fread(&length, sizeof(int), 1, m_pFile);

	char* buffer = new char[length+1];
	fread(buffer, sizeof(char), length, m_pFile);
	buffer[length] = '\0';
	result = buffer;
	delete [] buffer;
	buffer = NULL;
}

void CL_File::ReadLine(std::string & line)
{
	assert(m_bIsOpen && m_filemode==E_FileModes_ReadText);

	char* buff = new char[1000];

	if(fgets(buff, 1000, m_pFile)==NULL)
	{
		line = "";
		m_bEof = true;	
	}
	else
	{
		line = buff;
	}

	delete [] buff;
	buff = NULL;	
}

void CL_File::ReadLineWithLength(std::string & line, int length)
{
	assert(m_bIsOpen && m_filemode==E_FileModes_ReadText);

	char* buff = new char[length];

	if(fgets(buff, length, m_pFile)==NULL)
	{
		line = "";
		m_bEof = true;	
	}
	else
	{
		line = buff;
	}

	delete [] buff;
	buff = NULL;
}

//----------------------WRITING-----------------------//


void CL_File::WriteInt(int buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary);

	fwrite(&buffer, sizeof(buffer), 1, m_pFile);
}

void CL_File::WriteFloat(float buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary );

	fwrite(&buffer, sizeof(buffer), 1, m_pFile);
}

void CL_File::WriteDouble(double buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary );

	fwrite(&buffer, sizeof(buffer), 1, m_pFile);
}

void CL_File::WriteChar(char buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary );

	fwrite(&buffer, sizeof(buffer), 1, m_pFile);
}

void CL_File::WriteString(const std::string & buffer)
{
	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary );

	int length = buffer.length();

	char* charArrBuffer = new char[length+1]; //allocate for the '\0' as well
	strcpy(charArrBuffer, buffer.c_str());
	fwrite(charArrBuffer, sizeof(char), length, m_pFile);

	delete [] charArrBuffer;
	charArrBuffer = NULL;
}

void CL_File::WriteStringLengthPrefix(const std::string &  buffer)
{
	//Writes an integer that represents the length
	//Then writes a string with the obtained length

	//This is to be used in our own formats for ease

	assert( m_bIsOpen && m_filemode==E_FileModes_WriteBinary );

	int length = buffer.length();
	char* charArrBuffer = new char[length+1]; //allocate for the '\0' as well
	strcpy(charArrBuffer, buffer.c_str());
	
	fwrite(&length, sizeof(int), 1, m_pFile);
	fwrite(charArrBuffer, sizeof(char), length, m_pFile);
	
	delete [] charArrBuffer;
	charArrBuffer = NULL;
}


void CL_File::WriteLine(const std::string & line)
{
	assert(m_bIsOpen && m_filemode==E_FileModes_WriteText);

	fputs(line.c_str(), m_pFile);
	fputs("\n", m_pFile);
}


//--------------------------Private------------------------------
bool CL_File::CheckFileExists(const std::string & filename)
{
    /* try to open file to read */
    FILE *file;
    if (file = fopen(filename.c_str(), "r"))
	{
        fclose(file);
        return true;
    }
    return false;
}

std::string	CL_File::ObtainCFilemode(E_FileModes filemode, bool isOpen, const std::string & filename)
{
	std::string mode;
	bool exists = isOpen || CL_File::CheckFileExists(filename);
	switch(filemode)
	{
		case E_FileModes_ReadText:
		{
			if(!exists) //Creates a file if there isn't one
			{
				fclose(fopen(filename.c_str(), "w"));
			}
			mode = "r";
			break;
		}
		case E_FileModes_WriteText:
		{
			if(exists) //TODO: Add "overwrite" flag in arguments to control file better
			mode = "a";
			else
			mode = "w";
			break;
		}
		case E_FileModes_ReadBinary:
		{
			if(!exists) //Creates a file if there isn't one
			{
				fclose(fopen(filename.c_str(), "w"));
			}

			mode = "rb";
			break;
		}
		case E_FileModes_WriteBinary: //TODO: Add "overwrite" flag in arguments to control file better
		{
			if(exists)
			mode = "ab";
			else
			mode = "wb";
			break;
		}
		default:
		{
			assert( false ); //Invalid filemode used
		}
	}

	return mode;
}